/*
 * @(#)ContentMD5Module.java   0.3-3 06/05/2001
 *
 *  This file is part of the HTTPClient package
 *  Copyright (C) 1996-2001 Ronald Tschal�r
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA 02111-1307, USA
 *
 *  For questions, suggestions, bug-reports, enhancement-requests etc.
 *  I may be contacted at:
 *
 *  ronald@innovation.ch
 *
 *  The HTTPClient's home page is located at:
 *
 *  http://www.innovation.ch/java/HTTPClient/
 *
 */

package org.everrest.http.client;

import org.everrest.core.util.Logger;

import java.io.IOException;

/**
 * This module handles the Content-MD5 response header. If this header was sent
 * with a response and the entity isn't encoded using an unknown transport
 * encoding then an MD5InputStream is wrapped around the response input stream.
 * The MD5InputStream keeps a running digest and checks this against the
 * expected digest from the Content-MD5 header the stream is closed. An
 * IOException is thrown at that point if the digests don't match.
 *
 * @author Ronald Tschal�r
 * @version 0.3-3 06/05/2001
 */
class ContentMD5Module implements HTTPClientModule {

    private static final Logger log = Logger.getLogger(ContentMD5Module.class);

    // Constructors

    ContentMD5Module() {
    }

    // Methods

    /** Invoked by the HTTPClient. */
    public int requestHandler(Request req, Response[] resp) {
        return REQ_CONTINUE;
    }

    /** Invoked by the HTTPClient. */
    public void responsePhase1Handler(Response resp, RoRequest req) {
    }

    /** Invoked by the HTTPClient. */
    public int responsePhase2Handler(Response resp, Request req) {
        return RSP_CONTINUE;
    }

    /** Invoked by the HTTPClient. */
    public void responsePhase3Handler(Response resp, RoRequest req) throws IOException, ModuleException {
        if (req.getMethod().equals("HEAD"))
            return;

        String md5_digest = resp.getHeader("Content-MD5");
        String trailer = resp.getHeader("Trailer");
        boolean md5_tok = false;
        try {
            if (trailer != null)
                md5_tok = Util.hasToken(trailer, "Content-MD5");
        } catch (ParseException pe) {
            throw new ModuleException(pe.toString());
        }

        if ((md5_digest == null && !md5_tok) || resp.getHeader("Transfer-Encoding") != null)
            return;

        if (log.isDebugEnabled()) {
            if (md5_digest != null)
                log.debug("Received digest: " + md5_digest + " - pushing md5-check-stream");
            else
                log.debug("Expecting digest in trailer " + " - pushing md5-check-stream");
        }
        resp.inp_stream = new MD5InputStream(resp.inp_stream, new VerifyMD5(resp));
    }

    /** Invoked by the HTTPClient. */
    public void trailerHandler(Response resp, RoRequest req) {
    }
}

class VerifyMD5 implements HashVerifier {
    RoResponse resp;

    public VerifyMD5(RoResponse resp) {
        this.resp = resp;
    }

    public void verifyHash(byte[] hash, long len) throws IOException {
        String hdr;
        try {
            if ((hdr = resp.getHeader("Content-MD5")) == null)
                hdr = resp.getTrailer("Content-MD5");
        } catch (IOException ioe) {
            return;
        } // shouldn't happen

        if (hdr == null)
            return;

        byte[] ContMD5 = Codecs.base64Decode(hdr.trim().getBytes("8859_1"));

        for (int idx = 0; idx < hash.length; idx++) {
            if (hash[idx] != ContMD5[idx])
                throw new IOException("MD5-Digest mismatch: expected " + hex(ContMD5) + " but calculated " + hex(hash));
        }

    }

    /** Produce a string of the form "A5:22:F1:0B:53" */
    private static String hex(byte[] buf) {
        StringBuffer str = new StringBuffer(buf.length * 3);
        for (int idx = 0; idx < buf.length; idx++) {
            str.append(Character.forDigit((buf[idx] >>> 4) & 15, 16));
            str.append(Character.forDigit(buf[idx] & 15, 16));
            str.append(':');
        }
        str.setLength(str.length() - 1);

        return str.toString();
    }
}
